package goim

import (
	"bufio"
	"encoding/binary"
	"fmt"
	"log"
	"net"
	"sync/atomic"
	"time"
)

// Proto proto.
type Proto struct {
	PackLen   int32  // package length
	HeaderLen int16  // header length
	Ver       int16  // protocol version
	Operation int32  // operation for request
	Seq       int32  // sequence number chosen by client
	Body      []byte // body
}

const (
	opHeartbeat      = int32(2)
	opHeartbeatReply = int32(3)
)

const (
	rawHeaderLen = uint16(16)
	heart        = 10 * time.Second
)

var (
	aliveCount int64
)

func StartServer(address string) {
	listener, err := net.Listen("tcp", address)
	if err != nil {
		log.Println("Error listening", err.Error())
		return
	}
	for {
		conn, err := listener.Accept()
		fmt.Println(conn.RemoteAddr())
		if err != nil {
			fmt.Println("Error accepting", err.Error())
			return // 终止程序
		}
		go handleConn(conn)
	}
}

func handleConn(conn net.Conn) {

	atomic.AddInt64(&aliveCount, 1)
	quit := make(chan bool, 1)
	defer func() {
		close(quit)
		atomic.AddInt64(&aliveCount, -1)
	}()

	wr := bufio.NewWriter(conn)
	rd := bufio.NewReader(conn)
	proto := new(Proto)
	hbProto := new(Proto)
	key := 1
	lastHb := time.Now()

	for {
		if err := tcpReadProto(rd, proto); err != nil {
			log.Printf("key:%d tcpReadProto() error(%v)", key, err)
			quit <- true
			return
		}

		if proto.Operation == opHeartbeat {
			hbProto.Ver = 1
			hbProto.Operation = opHeartbeatReply
			hbProto.Seq = proto.Seq
			hbProto.Body = nil
			// 这里也简化了，实际还可作动态自适应
			if now := time.Now(); now.Sub(lastHb) > 5 {
				lastHb = now
				if err := tcpWriteProto(wr, hbProto); err != nil {
					log.Printf("key:%d tcpWriteProto() error(%v)", key, err)
					return
				}
				log.Printf("key:%d Write heartbeatReply", key)
				time.Sleep(heart)

				select {
				case <-quit:
					return
				default:
				}
			}
		}

	}
}

func tcpWriteProto(wr *bufio.Writer, proto *Proto) (err error) {
	// write
	if err = binary.Write(wr, binary.BigEndian, uint32(rawHeaderLen)+uint32(len(proto.Body))); err != nil {
		return
	}
	if err = binary.Write(wr, binary.BigEndian, rawHeaderLen); err != nil {
		return
	}
	if err = binary.Write(wr, binary.BigEndian, proto.Ver); err != nil {
		return
	}
	if err = binary.Write(wr, binary.BigEndian, proto.Operation); err != nil {
		return
	}
	if err = binary.Write(wr, binary.BigEndian, proto.Seq); err != nil {
		return
	}
	if proto.Body != nil {
		if err = binary.Write(wr, binary.BigEndian, proto.Body); err != nil {
			return
		}
	}
	err = wr.Flush()
	return
}

func tcpReadProto(rd *bufio.Reader, proto *Proto) (err error) {
	var (
		packLen   int32
		headerLen int16
	)
	// read
	if err = binary.Read(rd, binary.BigEndian, &packLen); err != nil {
		return
	}
	if err = binary.Read(rd, binary.BigEndian, &headerLen); err != nil {
		return
	}
	if err = binary.Read(rd, binary.BigEndian, &proto.Ver); err != nil {
		return
	}
	if err = binary.Read(rd, binary.BigEndian, &proto.Operation); err != nil {
		return
	}
	if err = binary.Read(rd, binary.BigEndian, &proto.Seq); err != nil {
		return
	}
	var (
		n, t    int
		bodyLen = int(packLen - int32(headerLen))
	)
	if bodyLen > 0 {
		proto.Body = make([]byte, bodyLen)
		for {
			if t, err = rd.Read(proto.Body[n:]); err != nil {
				return
			}
			if n += t; n == bodyLen {
				break
			}
		}
	} else {
		proto.Body = nil
	}
	return
}
